/**
 * @(#)ConfigurationModel.java
 * 
 * @author Alan Salnikov
 * @author Mankin Yuen
 * @author William Clark
 */
package edg.acm;

import java.util.*;
import edg.db.Simulator;

/**
 * Provides a library to create, manipulate, and validate the configuration
 * model.
 */
public class ConfigurationModel {
	// internal variables of the CM
	int id;
	int user;
	Map<String, Object> db = new HashMap<String, Object>();

	/**
	 * 
	 * @return a new {@link ConfigurationModel}
	 */
	public static ConfigurationModel createNewConfigModel() {
		return new ConfigurationModel();
	}

	/**
	 * Sets the ID of this instance.
	 * 
	 * @param a
	 *            the ID
	 * @throws IllegalValueException
	 *             if the provided ID is invalid (0)
	 * @throws ConfigurationException
	 *             if this instance already has an ID
	 */
	public void setID(int a) {
		if (a == 0)
			throw new IllegalValueException("Not a valid ID", a);
		if (a == id)
			return;
		if (id != 0)
			throw new ConfigurationException("This model already has an ID");
		id = a;
	}

	/**
	 * 
	 * @return the ID of this instance, or 0 if unset
	 */
	public int getID() {
		return id;
	}

	/**
	 * Sets the ID of the user who provided the data for this instancee,
	 * unsetting the user name if the argument is null.
	 * 
	 * @param a
	 *            the user's ID
	 */
	public void setUser(int a) {
		user = a;
	}

	/**
	 * 
	 * @return the ID of the user who provided the data for this instance, or
	 *         null if unset
	 */
	public int getUser() {
		return user;
	}

	/**
	 * Verifies the parameter named <tt>param</tt> exists. If <tt>value</tt> is
	 * null, unsets the parameter, otherwise it validates the value using
	 * {@link verify()} and sets the value of the parameter if it validates.
	 * This function will be tested extensively during our test plan phase to
	 * ensure parameters are set to valid values and never to invalid values.
	 * 
	 * @param param
	 *            the parameter name
	 * @param value
	 *            the desired value
	 * @return true if the parameter was set
	 * @throws InvalidParameterException
	 *             if <tt>param</tt> is not a valid parameter
	 * @throws IllegalValueException
	 *             if <tt>value</tt> is not within the bounds of the parameter
	 */
	public boolean setValue(String param, Object value) {
		if (value == null) {
			ConfigurationParameter.getParameter(param);
			db.remove(param);
		} else {
			verify(param, value);
			db.put(param, value);
		}
		return true;
	}

	/**
	 * Verifies the parameter named <tt>param</tt> exists and returns the value.
	 * This function will be tested to ensure that values are retrieved from
	 * proper instances of the specified parameter.
	 * 
	 * @param param
	 * @return the value of the parameter, or null if unset
	 * @throws InvalidParameterException
	 *             if <tt>param</tt> is not a valid parameter
	 */
	public Object getValue(String param) {
		ConfigurationParameter.getParameter(param);
		return db.get(param);
	}

	/**
	 * Verifies that <tt>p</tt> exists, and that <tt>v</tt> is of the
	 * appropriate type and within bounds.
	 * 
	 * @param p
	 *            the parameter
	 * @param v
	 *            the value
	 * @return true if the arguments pass
	 * @throws InvalidParameterException
	 *             if <tt>p</tt> is not a valid parameter
	 * @throws IllegalValueException
	 *             if <tt>v</tt> is not of the correct type or is out of bounds
	 */
	private boolean verify(String p, Object v) {
		ConfigurationParameter param = ConfigurationParameter.getParameter(p);

		if (!param.getType().isInstance(v))
			throw new IllegalValueException("Invalid parameter type", v);

		if (v instanceof Number) {
			Number num = (Number) v;

			if (param.getMaximum() != null && num.doubleValue() > param.getMaximum().doubleValue())
				throw new IllegalValueException("Value is above maximum bound", v);

			if (param.getMinimum() != null && num.doubleValue() < param.getMinimum().doubleValue())
				throw new IllegalValueException("Value is below minimum bound", v);
		}

		return true;

	}

	/**
	 * Verifies that values are present for all required parameters.
	 * 
	 * @return true if the model is valid
	 * @throws ValidationException
	 *             if any required parameters are missing
	 */
	public boolean validate() throws ValidationException {
		List<String> missing = new LinkedList<String>();

		for (ConfigurationParameter param : ConfigurationParameter.getParameters())
			if (param.isRequired())
				if (!db.containsKey(param.getName()))
					missing.add(param.getName());

		if (!missing.isEmpty())
			throw new ValidationException(missing);

		return true;
	}

	/**
	 * Convenience method that uses the Database module's save method to save
	 * the instance to the database, obtaining an ID if one has not already been
	 * obtained.
	 * 
	 * @return true if the instance was successfully saved
	 */
	public boolean save() {
		int i = Simulator.save(this);
		if (i != 0) {
			setID(i);
			return true;
		} else
			return false;
	}
}